/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.spring;

import static org.springframework.util.ObjectUtils.isEmpty;
import static org.springframework.util.StringUtils.hasLength;
import static org.springframework.util.StringUtils.tokenizeToStringArray;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;

import com.jianggujin.dbfly.mybatis.JConfiguration;
import com.jianggujin.dbfly.mybatis.JXMLConfigBuilder;
import com.jianggujin.dbfly.mybatis.JXMLMapperBuilder;
import com.jianggujin.dbfly.util.JBeanUtils;

public class JSqlSessionFactoryBean extends SqlSessionFactoryBean
        implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    private static final Log LOGGER = LogFactory.getLog(JSqlSessionFactoryBean.class);
    private Properties dbfly = null;

    public JSqlSessionFactoryBean() {
        // 更改为当前类
        setEnvironment(JSqlSessionFactoryBean.class.getSimpleName());
    }

    @Override
    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

        Configuration configuration;

        JXMLConfigBuilder xmlConfigBuilder = null;

        Configuration configurationCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "configuration");
        Resource configLocationCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "configLocation");
        Properties configurationPropertiesCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class,
                "configurationProperties");

        if (configurationCopy != null) {
            configuration = configurationCopy;
            if (configuration.getVariables() == null) {
                configuration.setVariables(configurationPropertiesCopy);
            } else if (configurationPropertiesCopy != null) {
                configuration.getVariables().putAll(configurationPropertiesCopy);
            }
        } else if (configLocationCopy != null) {
            xmlConfigBuilder = new JXMLConfigBuilder(configLocationCopy.getInputStream(), null,
                    configurationPropertiesCopy);
            configuration = xmlConfigBuilder.getConfiguration();
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(
                        "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
            }
            configuration = new JConfiguration();
            if (configurationPropertiesCopy != null) {
                configuration.setVariables(configurationPropertiesCopy);
            }
        }
        if (this.dbfly != null) {
            ((JConfiguration) configuration).getDBFlyConfiguration().setProperties(this.dbfly);
        }

        ObjectFactory objectFactoryCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "objectFactory");
        if (objectFactoryCopy != null) {
            configuration.setObjectFactory(objectFactoryCopy);
        }

        ObjectWrapperFactory objectWrapperFactoryCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class,
                "objectWrapperFactory");
        if (objectWrapperFactoryCopy != null) {
            configuration.setObjectWrapperFactory(objectWrapperFactoryCopy);
        }

        Class<? extends VFS> vfsCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "vfs");
        if (vfsCopy != null) {
            configuration.setVfsImpl(vfsCopy);
        }

        String typeAliasesPackageCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "typeAliasesPackage");
        if (hasLength(typeAliasesPackageCopy)) {
            String[] typeAliasPackageArray = tokenizeToStringArray(typeAliasesPackageCopy,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            Class<?> typeAliasesSuperTypeCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class,
                    "typeAliasesSuperType");
            for (String packageToScan : typeAliasPackageArray) {
                configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                        typeAliasesSuperTypeCopy == null ? Object.class : typeAliasesSuperTypeCopy);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
                }
            }
        }

        Class<?>[] typeAliasesCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "typeAliases");
        if (!isEmpty(typeAliasesCopy)) {
            for (Class<?> typeAlias : typeAliasesCopy) {
                configuration.getTypeAliasRegistry().registerAlias(typeAlias);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered type alias: '" + typeAlias + "'");
                }
            }
        }

        Interceptor[] pluginsCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "plugins");
        if (!isEmpty(pluginsCopy)) {
            for (Interceptor plugin : pluginsCopy) {
                configuration.addInterceptor(plugin);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered plugin: '" + plugin + "'");
                }
            }
        }

        String typeHandlersPackageCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "typeHandlersPackage");
        if (hasLength(typeHandlersPackageCopy)) {
            String[] typeHandlersPackageArray = tokenizeToStringArray(typeHandlersPackageCopy,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeHandlersPackageArray) {
                configuration.getTypeHandlerRegistry().register(packageToScan);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
                }
            }
        }

        TypeHandler<?>[] typeHandlersCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "typeHandlers");
        if (!isEmpty(typeHandlersCopy)) {
            for (TypeHandler<?> typeHandler : typeHandlersCopy) {
                configuration.getTypeHandlerRegistry().register(typeHandler);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered type handler: '" + typeHandler + "'");
                }
            }
        }

        DatabaseIdProvider databaseIdProviderCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class,
                "databaseIdProvider");

        DataSource dataSourceCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "dataSource");
        if (databaseIdProviderCopy != null) {// fix #64 set databaseId before parse mapper xmls
            try {
                configuration.setDatabaseId(databaseIdProviderCopy.getDatabaseId(dataSourceCopy));
            } catch (SQLException e) {
                throw new NestedIOException("Failed getting a databaseId", e);
            }
        }

        Cache cacheCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "cache");
        if (cacheCopy != null) {
            configuration.addCache(cacheCopy);
        }

        if (xmlConfigBuilder != null) {
            try {
                xmlConfigBuilder.parse();

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Parsed configuration file: '" + configLocationCopy + "'");
                }
            } catch (Exception ex) {
                throw new NestedIOException("Failed to parse config resource: " + configLocationCopy, ex);
            } finally {
                ErrorContext.instance().reset();
            }
        }

        TransactionFactory transactionFactoryCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class,
                "transactionFactory");
        if (transactionFactoryCopy == null) {
            transactionFactoryCopy = new SpringManagedTransactionFactory();
            setTransactionFactory(transactionFactoryCopy);
        }

        // EnvironmentAware requires spring 3.1
        // SqlSessionFactoryBean.class.getSimpleName()
        String environmentCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "environment");
        configuration.setEnvironment(new Environment(environmentCopy, transactionFactoryCopy, dataSourceCopy));

        Resource[] mapperLocationsCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class, "mapperLocations");
        if (!isEmpty(mapperLocationsCopy)) {
            for (Resource mapperLocation : mapperLocationsCopy) {
                if (mapperLocation == null) {
                    continue;
                }

                try {
                    JXMLMapperBuilder xmlMapperBuilder = new JXMLMapperBuilder(mapperLocation.getInputStream(),
                            configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                    ErrorContext.instance().reset();
                }

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
                }
            }
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
            }
        }

        SqlSessionFactoryBuilder sqlSessionFactoryBuilderCopy = JBeanUtils.getValue(this, SqlSessionFactoryBean.class,
                "sqlSessionFactoryBuilder");
        return sqlSessionFactoryBuilderCopy.build(configuration);
    }

    public void setDbfly(Properties dbfly) {
        this.dbfly = dbfly;
    }
}
